﻿using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
using GuiLabs.Utils.Actions;

namespace DynamicGeometry
{
    public class AddFigureAction : GeometryAction
    {
        public AddFigureAction(Drawing drawing, IFigure figure)
            : base(drawing)
        {
            Figure = figure;
        }

        public IFigure Figure { get; set; }

        protected override void ExecuteCore()
        {
            Drawing.Figures.Add(Figure);
        }

        protected override void UnExecuteCore()
        {
            Drawing.Figures.Remove(Figure);
        }
    }

    public class RemoveFigureAction : GeometryAction
    {
        public RemoveFigureAction(Drawing drawing, IFigure figure)
            : base(drawing)
        {
            Figure = figure;
        }

        public IFigure Figure { get; set; }

        protected override void ExecuteCore()
        {
            Drawing.Figures.Remove(Figure);
        }

        protected override void UnExecuteCore()
        {
            Drawing.Figures.Add(Figure);
        }
    }

    public class RemoveFiguresAction : GeometryAction
    {
        public RemoveFiguresAction(Drawing drawing, IEnumerable<IFigure> figures)
            : base(drawing)
        {
            Figures = figures.ToArray();
        }

        public IEnumerable<IFigure> Figures { get; set; }

        protected override void ExecuteCore()
        {
            Drawing.Figures.Remove(Figures);
        }

        protected override void UnExecuteCore()
        {
            Drawing.Figures.AddRange(Figures);
        }
    }

    public class MoveAction : GeometryAction
    {
        public MoveAction(
            Drawing drawing,
            IEnumerable<IMovable> points,
            Point offset,
            IEnumerable<IFigure> toRecalculate)
            : base(drawing)
        {
            Points = points;
            Offset = offset;
            ToRecalculate = toRecalculate;
        }

        public IEnumerable<IFigure> ToRecalculate { get; set; }
        public IEnumerable<IMovable> Points { get; set; }
        public Point Offset { get; set; }

        protected override void ExecuteCore()
        {
            Points.Move(Offset);
            Recalculate();
        }

        void Recalculate()
        {
            if (ToRecalculate != null)
            {
                ToRecalculate.ForEach(f => f.RecalculateAndUpdateVisual());
            }
        }

        protected override void UnExecuteCore()
        {
            Points.Move(Offset.Minus());
            Recalculate();
        }

        public override bool TryToMerge(IAction followingAction)
        {
            MoveAction next = followingAction as MoveAction;
            if (next != null
                && next.Points == this.Points)
            {
                Points.Move(next.Offset);
                Offset = Offset.Plus(next.Offset);
                Recalculate();
                return true;
            }
            return false;
        }
    }

    public class SetPropertyAction : AbstractAction
    {
        public SetPropertyAction(
            object target, PropertyInfo property, object newValue)
        {
            Target = target;
            Property = property;
            NewValue = newValue;
        }

        public object Target { get; set; }
        public PropertyInfo Property { get; set; }
        public object NewValue { get; set; }
        public object OldValue { get; set; }

        protected override void ExecuteCore()
        {
            OldValue = Property.GetValue(Target, null);
            Property.SetValue(Target, NewValue, null);
        }

        protected override void UnExecuteCore()
        {
            Property.SetValue(Target, OldValue, null);
        }

        public override bool TryToMerge(IAction followingAction)
        {
            SetPropertyAction next = followingAction as SetPropertyAction;
            if (next != null
                && next.Target == this.Target
                && next.Property == this.Property)
            {
                this.NewValue = next.NewValue;
                Property.SetValue(Target, NewValue, null);
                return true;
            }
            return false;
        }
    }
}
